package com.zrrd.gmall.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.zrrd.gmall.common.result.Result;
import com.zrrd.gmall.common.result.ResultCodeEnum;
import com.zrrd.gmall.common.util.IpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Component
public class AuthGlobaFilter implements GlobalFilter {

    //操作redis的对象
    @Autowired
    private RedisTemplate redisTemplate;
    //匹配路径的工具类
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    //需要登录校验的路径
    @Value("${authUrls.url}")
    private String authUrls;

    //过滤的方法
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取当前请求和响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //获取当前请求过来的Url
        String path = request.getURI().getPath();
        System.out.println("当前请求地址====>" + path);

        //如果是内部的请求直接拦截 不允许访问
        if (antPathMatcher.match("/**/inner/**", path)) {
            //给请求者响应一个数据
            return this.out(response, ResultCodeEnum.PERMISSION);
        }
        //获取用户ID
        String userId = this.getUserId(request);
        //ip 不一致 token被盗用
        if ("-1".equals(userId)) {
            //给请求者响应一个数据
            return this.out(response, ResultCodeEnum.PERMISSION);
        }

        //用户登录认证
        if (antPathMatcher.match("/api/**/auth/**", path)) {
            if (StringUtils.isEmpty(userId)) {
                return this.out(response, ResultCodeEnum.LOGIN_AUTH);
            }
        }

        //验证url（判断访问的地址 是否需要登录）
        for (String authUrl : authUrls.split(",")) {
            //当前请求过来的路径中 包含了 配置文件中需要校验登录的内容（list.html）并且此时用户ID为空
            if (path.indexOf(authUrl) != -1 && StringUtils.isEmpty(userId)) {
                //给响应对象设置一个状态码 303代表需要重定向
                response.setStatusCode(HttpStatus.SEE_OTHER);
                //设置重定向请求地址
                response.getHeaders().set(HttpHeaders.LOCATION,
                        "http://www.gmall.com/login.html?originUrl="+request.getURI());
                //完成重定向
                return response.setComplete();
            }
        }

        //获取临时用户的ID
        String userTempId = this.getUserTempId(request);


        //如果临时用户ID不为空 或者 当前登录用户ID不为空
        if(!StringUtils.isEmpty(userId) || !StringUtils.isEmpty(userTempId)){
            //如果能走到这里 代表用户登录了 将用户id传递给后端
            if(!StringUtils.isEmpty(userId)){
                request.mutate().header("userId",userId).build();
            }
            if(!StringUtils.isEmpty(userTempId)){
                request.mutate().header("userTempId",userTempId).build();
            }
        }
        //响应
        return chain.filter(exchange.mutate().request(request).build());
    }

    //获取当前用户Id的方法
    private String getUserId(ServerHttpRequest request) {
        String token = "";
        //获取请求头
        List<String> tokenList = request.getHeaders().get("token");
        if (tokenList != null) {
            //从请求头中获取token
            token = tokenList.get(0);
        } else {
            //如果请求头中没有 从Cookie查找
            MultiValueMap<String, HttpCookie> cookies = request.getCookies();
            HttpCookie cookie = cookies.getFirst("token");
            if (cookie != null) {
                token = URLDecoder.decode(cookie.getValue());
            }
        }
        //以上走完token不一定有值 非空判断
        if (!StringUtils.isEmpty(token)) {
            //从redis中获取数据
            String userStr = (String) redisTemplate.opsForValue().get("user:login:" + token);
            //解析该json数据
            JSONObject userJson = JSONObject.parseObject(userStr);
            //获取IP
            String ip = userJson.getString("ip");
            //获取当前用户的IP
            String currentIp = IpUtil.getGatwayIpAddress(request);
            //ip对比
            if (ip.equals(currentIp)) {
                //获取用户id 并且返回
                return userJson.getString("userId");
            } else {
                return "-1";
            }

        }
        return "";
    }

    //获取临时用户的ID
    private String getUserTempId(ServerHttpRequest request){
        String userTempId = "";
        List<String> list = request.getHeaders().get("userTempId");
        if(list != null){
            userTempId = list.get(0);
        }else{
            MultiValueMap<String, HttpCookie> cookies = request.getCookies();
            HttpCookie cookie = cookies.getFirst("userTempId");
            if(cookie != null){
                userTempId = URLDecoder.decode(cookie.getValue());
            }
        }
        return userTempId;
    }

    //响应信息的方法(用户没有权限)
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        //创建返回结果对象
        Result<Object> result = Result.build(null, resultCodeEnum);
        //将对象转为json并且获取该json字符串的字符数组
        byte[] bytes = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        //响应之前添加请求头 告知浏览器响应的类型 和 编码格式
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        //进行响应
        return response.writeWith(Mono.just(wrap));
    }
}
